package mango.orm.impl;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import mango.orm.Dao;
import mango.orm.OrmException;
import mango.orm.OrmFactory;
import mango.orm.SQLParameter;
import mango.orm.DBSession;
import mango.orm.DBSessionFactory;
import mango.orm.connect.Connector;
import mango.orm.entity.Entity;
import mango.orm.entity.EntityPrmMaker;



@SuppressWarnings({ "rawtypes", "unchecked" })
public class DaoImpl implements Dao {
    private int daoType;
    private Batcher batcher;
    private DBSession session;
    private ResultSet result;
    private boolean selfClose;
    private Class<? extends Entity> queryResultType;
    private Integer start = null;
    private Integer limit = null;
    private boolean used = false;
    private String dbType;
    private int totalCount = -1;
    private int pageCount = 0;
    private int curPage = 0;

    public DaoImpl(DBSession session, String sql, boolean selfClose) {
        this.session = session;
        this.batcher = new Batcher(session, sql);
        this.queryResultType = Entity.class;
        this.selfClose = selfClose;
        this.dbType = this.session.getConnector().getDbType();
        this.daoType = OrmFactory.getSQLMaker(this.dbType).getDaoTypeBySql(sql);
    }

    public Dao setType(int type) {
        this.daoType = type;
        return this;
    }

    public int getType() {
        return this.daoType;
    }

    public Dao setStart(int firstResult) {
        if (firstResult < 0) {
            throw new OrmException("error firstResult : " + firstResult);
        }
        this.start = new Integer(firstResult);
        return this;
    }

    public Dao setLimit(int fatchSize) {
        if (fatchSize <= 0) {
            throw new OrmException("error fatchSize : " + fatchSize);
        }
        this.limit = new Integer(fatchSize);
        return this;
    }

    public Dao setResultType(Class<? extends Entity> clazz) {
        this.queryResultType = clazz;
        return this;
    }

    public Class getResultType() {
        return this.queryResultType;
    }
    
    public Dao setParameter(Object... val) {
        if (val != null) {
            for (int i = 0; i < val.length; i++) {
                this.batcher.addParameter(new SQLParameter(i+1, val[i]));
            }
        }
        return this;
    }

    public Dao setParamByName(String name, Object val) {
        this.batcher.addParameter(new SQLParameter(name, val));
        return this;
    }

    public Dao setParameter(Entity entity) {
        if (this.daoType == Dao.TYPE_QUERY) {
            this.queryResultType = entity.getClass();
            this.batcher.addBathParameters(EntityPrmMaker.getQueryParameters(entity));
        }
        else if (this.daoType == Dao.TYPE_CREATE) {
            this.batcher.addBathParameters(EntityPrmMaker.getInsertParameters(entity));
        }
        else if (this.daoType == Dao.TYPE_UPDATE) {
            this.batcher.addBathParameters(EntityPrmMaker.getUpdateParameters(entity));
        }
        else if (this.daoType == Dao.TYPE_DELETE) {
            this.batcher.addBathParameters(EntityPrmMaker.getDeleteParameters(entity));
        }
        else {
            throw new OrmException("can't setParameter by entity!");
        }
        return this;
    }
    
    public Dao setSubSql(int index, String subsql) {
        this.batcher.setSubSql(index, subsql);
        return this;
    }

    public Dao setSubSql(String... subsql) {
        this.batcher.setSubSql(subsql);
        return this;
    }

    public boolean isUsed() {
        return used;
    }

    private void setUsed(boolean used) {
        this.used = used;
    }

    public ResultSet executeQuery() throws SQLException {
        this.setFatch();
        this.result = this.batcher.executeQuery();
        this.setUsed(true);
        return this.result;
    }

    private void setFatch() {
        this.batcher.setExecuteSql(this.batcher.getSql());
        if (this.limit == null || this.start == null) {
            return;
        }
        if (!this.batcher.isCallable()) {
            this.batcher.setExecuteSql(OrmFactory.getSQLMaker(this.dbType).getLimitSql(
                    this.batcher.getSql(), this.start.intValue(), this.limit.intValue()));
            return;
        }
    }

    public Iterator<Entity> queryIterator() {
        try {
            this.executeQuery();
            return new EntityIterator(this, this.session, this.selfClose, this.result, this.queryResultType);
        }
        catch (SQLException e) {
            DBSessionFactory.closeSession(session);
            throw new OrmException(this.batcher.getSqlInfo(), e);
        }
    }
    
    public boolean hasNextPage() {
        if (this.limit == null) {
            this.close();
            throw new OrmException("must be identified the fatchSize by setLimit method!");
        }
        this.pageCount = this.getTotalCount() / this.limit + (this.getTotalCount() % this.limit == 0 ? 0 : 1);
        if (this.curPage < this.pageCount) {
            return true;
        }
        this.close();
        return false;
    }

    public Iterator<Entity> nextPage() {
        DBSession pageSession = DBSessionFactory.getSession(this.session.getConnector());
        Batcher pageBatcher = this.batcher.clone(pageSession);
        pageBatcher.setExecuteSql(OrmFactory.getSQLMaker(this.dbType).getLimitSql(this.batcher.getSql(), this.curPage * this.limit, this.limit));
        ResultSet pageResult = null;
        try {
            pageResult = pageBatcher.executeQuery();
            Iterator<Entity> iter = new EntityIterator(this, pageSession, true, pageResult, this.queryResultType);
            this.curPage++;
            return iter;
        }
        catch (SQLException e) {
            this.close();
            if (pageResult != null) {
                try {
                    pageResult.close();
                }
                catch (SQLException e1) {
                }
            }
            throw new OrmException(this.batcher.getSqlInfo(), e);
        }
    }
    
    public Iterator<String> iterString() {
        try {
            this.executeQuery();
            return new StringIterator(this, this.session, this.selfClose, this.result, String.class);
        }
        catch (SQLException e) {
            this.close();
            throw new OrmException(this.batcher.getSqlInfo(), e);
        }
    }

    public List<Entity> listQuery() {
        List<Entity> list = new ArrayList<Entity>();
        Iterator<Entity> iter = this.queryIterator();
        while (iter.hasNext()) {
            list.add(iter.next());
        }
        this.close();
        return list;
    }
    
    public List<String> listQueryString() {
        List<String> list = new ArrayList<String>();
        Iterator<String> iter = this.iterString();
        while (iter.hasNext()) {
            list.add(iter.next());
        }
        this.close();
        return list;
    }

    public Entity uniqueResult() {
        Entity o = null;
        EntityIterator iter = (EntityIterator) this.queryIterator();
        if (iter.hasNext()) {
            o = iter.next();
        }
        this.close();
        return o;
    }

    public String getStringValue() {
        try {
            this.executeQuery();
            if (this.result.next()) {
                if (this.result.getMetaData().getColumnCount() >= 1) {
                    String ret = this.result.getString(1);
                    this.close();
                    return ret;
                }
            }
            this.close();
            return null;
        }
        catch (SQLException e) {
            this.close();
            throw new OrmException(this.batcher.getSqlInfo(), e);
        }
    }
    
    public int getIntValue() {
        return Integer.parseInt(this.getStringValue());
    }
    
    public long getLongValue() {
        return Long.parseLong(this.getStringValue());
    }
    
    public double getDoubleValue() {
        return Double.parseDouble(this.getStringValue());
    }

    public int getTotalCount() {
        if (Dao.TYPE_QUERY != this.daoType) {
            return 0;
        }
        if (this.totalCount != -1) {
            return this.totalCount;
        }
        ResultSet count = null;
        try {
            DBSession countSession = null;
            try {
                countSession = DBSessionFactory.getSession(Connector.getConnector(this.session.getConnectorName()));
                Batcher countBatcher = this.batcher.clone(countSession);
                countBatcher.setExecuteSql(new DefaultSQLMaker().getCountSql(countBatcher.getSql()));
                count = countBatcher.executeQuery();
                count.next();
                this.totalCount = count.getInt(1);
                return this.totalCount;
            }
            finally {
                if (countSession != null) {
                    DBSessionFactory.closeSession(countSession);
                }
            }
        }
        catch (SQLException e) {
            throw new OrmException(this.batcher.getSqlInfo(), e);
        }
    }

    public int executeUpdate() {
        try {
            int ret = this.batcher.executeUpdate();
            this.setUsed(true);
            this.close();
            return ret;
        }
        catch (SQLException e) {
            this.close();
            throw new OrmException(this.batcher.getSqlInfo(), e);
        }
    }

    public void executeBatch() {
        try {
            if (!this.checkBatch()) {
                return;
            }
            this.batcher.executeBatch();
            this.setUsed(true);
            this.close();
        }
        catch (SQLException e) {
            this.close();
            throw new OrmException(this.batcher.getSqlInfo(), e);
        }
    }

    private boolean checkBatch() {
        if (this.daoType == Dao.TYPE_QUERY) {
            this.batcher.setBatchable(false);
        }
        return this.batcher.isBatchable();
    }

    public boolean execute() {
        try {
            boolean ret = this.batcher.execute();
            this.setUsed(true);
            this.close();
            return ret;
        }
        catch (SQLException e) {
            this.close();
            throw new OrmException(this.batcher.getSqlInfo(), e);
        }
    }

    public void close() {
        if (this.selfClose) {
            DBSessionFactory.closeSession(session);
        }
        this.batcher = null;
        this.session = null;
        this.result = null;
        this.queryResultType = null;
        this.start = null;
        this.limit = null;
        this.dbType = null;
    }
}
